package ai4games_tests.tests;

import game2dai.entities.Building;
import game2dai.maths.Vector2D;
import game2dai.utils.Message;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.TreeSet;

import processing.core.PApplet;

public class BinPacking extends PApplet {

	ArrayList<Block> blocks;

	String xml001 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

	String bline1 = 	"<building name=\"{0}\">";
	String pos = 		"<pos x=\"{0}\" y = \"{1}\" />";
	String point = 		"<point x=\"{0}\" y=\"{1}\" />";
	
	Building[] buildings;
	
	int aw = 8000;
	int ah = 8000;

	int areaBorder = 40;
	int areaBorder2 = 2 * areaBorder;
	
	int minDim = 40 + areaBorder2;
	int minDim2 = 2 * minDim;
	int maxArea = 3 * minDim2 * minDim2;

	float scale = 1f;
	float sw = 1.1f;

	String buildingXMLfile = "../src/d002_building.xml";
	ArrayList<String> buildingXML = new ArrayList<String>();
	
	String graphXMLfile = "../src/d002_graph.xml";	
	ArrayList<String> graphXML = new ArrayList<String>();
	
	
	public void setup(){
		size(1010,1010);

		scale = 0.9f * width/aw;
		stroke(0);
		this.strokeWeight(sw);
		rectMode(CORNERS);
		noFill();
		
		blocks = new ArrayList<Block>();
		
		buildings = Building.makeFromXML("building_shapes.xml");
		Message.println("{0} building types loaded", buildings.length);
		
		splitCity();
		println("Nbr areas = " + blocks.size());
	}

	public void mouseClicked(){
		blocks.clear();
		splitCity();
		println("Nbr areas = " + blocks.size());
	}
	
	public void draw(){
		background(255);
		pushMatrix();
		translate(width/2, height/2);
		scale(scale);
		for(Block block : blocks){
			fill(block.col);
			rect(block.x0, block.y0, block.x1, block.y1);
		}
		popMatrix();
	}
	
	public void keyReleased(){
		if(key == 'n' || key == 'N'){
			blocks.clear();
			splitCity();
		}
		else if(key == 's' || key == 'S'){
			makeBuildingFile();
			makeGraphFile();
		}
	}
	
	public void makeGraphFile(){
		graphXML = new ArrayList<String>();
		graphXML.add(xml001);
		graphXML.add("<graph>\n");
		int nodeID = 1;
		
		TreeSet<PointNode> nodeSet = new TreeSet<PointNode>(new PointNodeXcomparitor());
		PointNode[] nodeArray;

		for(Block block : blocks){
			int x0 = block.x0;
			int y0 = block.y0;
			int x1 = block.x1;
			int y1 = block.y1;
			nodeSet.add(new PointNode(nodeID++, x0, y0));
			nodeSet.add(new PointNode(nodeID++, x0, y1));
			nodeSet.add(new PointNode(nodeID++, x1, y0));
			nodeSet.add(new PointNode(nodeID++, x1, y1));
		}

		graphXML.add("<nodes>\n");
		for(PointNode node : nodeSet){
			graphXML.add(Message.build("<node id=\"{0}\" x=\"{1}\" y=\"{2}\" />", s(node.id), s(node.x), s(node.y)));
		}
		graphXML.add("</nodes>\n");
		graphXML.add("<edges>");
		nodeArray = nodeSet.toArray(new PointNode[nodeSet.size()]);
		for(int i = 1; i < nodeArray.length; i++){
			if(nodeArray[i-1].x == nodeArray[i].x && abs(nodeArray[i-1].y - nodeArray[i].y) <= 2.5 * minDim2){
				graphXML.add(Message.build("<edge from=\"{0}\" to=\"{1}\" cost_out=\"{2}\" cost_back=\"{2}\" />", 
						s(nodeArray[i-1].id), s(nodeArray[i].id), s(nodeArray[i].y - nodeArray[i-1].y)));
			}
		}
		Arrays.sort(nodeArray, new PointNodeYcomparitor());
		for(int i = 1; i < nodeArray.length; i++){
			if(nodeArray[i-1].y == nodeArray[i].y && abs(nodeArray[i-1].x - nodeArray[i].x) <= 2.5 * minDim2)
				graphXML.add(Message.build("<edge from=\"{0}\" to=\"{1}\" cost_out=\"{2}\" cost_back=\"{2}\" />", 
						s(nodeArray[i-1].id), s(nodeArray[i].id), s(nodeArray[i].x - nodeArray[i-1].x)));
		}
		graphXML.add("</edges>\n");
		graphXML.add("</graph>\n");
	
		String[] lines = graphXML.toArray(new String[graphXML.size()]);
		saveStrings(graphXMLfile, lines);
		
		System.out.println("Nbr of nodes = " + nodeSet.size());	
		System.out.println("Graph file saved");
	}
	
	public void makeBuildingFile(){
		buildingXML = new ArrayList<String>();
		buildingXML.add(xml001);
		buildingXML.add("<buildings>\n");
		int bn = 1;
		float x,y;
		
		for(Block area : blocks){
			float x0 = area.x0;
			float y0 = area.y0;
			float x1 = area.x1;
			float y1 = area.y1;
			float w = x1 - x0 - areaBorder2;
			float h = y1 - y0 - areaBorder2;
			int bnbr = (int) random(buildings.length);
			int rot = (int) random(1, 4);
			double temp, tempY;
			Vector2D[] contour = buildings[bnbr].contour();

			for(int i = 0; i < contour.length; i++){
				switch(rot){
				case 1:
					tempY = 1 - contour[i].x;
					contour[i].x = contour[i].y;
					contour[i].y = tempY;
					break;
				case 2:
					contour[i].x = 1 - contour[i].x;
					contour[i].y = 1 - contour[i].y;
					break;
				case 3:
					temp = 1 - contour[i].y;
					contour[i].y = contour[i].x;
					contour[i].x = temp;
					break;
				default:
					System.out.println("=============================================================================================");
				}
			}
			buildingXML.add(Message.build(bline1, "Block " + bn++));
			buildingXML.add(Message.build(pos, s(x0), s(y0)));
			buildingXML.add("<contour>");
			for(int i = 0; i < contour.length; i++){
				x =  (float) (w * contour[i].x + areaBorder);
				y =  (float) (h * contour[i].y + areaBorder);
				buildingXML.add(Message.build(point, s(x), s(y)));
			}
			buildingXML.add("</contour>");			
			buildingXML.add("</building>\n\n");
		}
		buildingXML.add("</buildings>\n");
		String[] lines = buildingXML.toArray(new String[buildingXML.size()]);
		saveStrings(buildingXMLfile, lines);
		System.out.println("Building file saved");
	}
	
	public String s(float n){
		return Float.toString(n);
	}
	
	public String s(int n){
		return Integer.toString(n);
	}
	
	public void splitCity(){
		blocks.clear();
		splitArea(-aw/2,-ah/2,aw/2,ah/2);
	}
	
	public void splitArea(int x0, int y0, int x1, int y1){
		int dx = x1 - x0;
		int dy = y1 - y0;
		int splitPos = 0;
		int area = (x1-x0)*(y1-y0);
		if(area > maxArea || (dx > minDim2 && dy > minDim2 && random(1.0f) > 0.8f) ){
			if(dx > dy){
				splitPos = x0 + minDim + (int)(random(dx - minDim2));
				splitArea(x0, y0, splitPos, y1);
				splitArea(splitPos, y0, x1, y1);
			}
			else {
				splitPos = y0 + minDim + (int)(random(dy - minDim2));
				splitArea(x0, y0, x1, splitPos);				
				splitArea(x0, splitPos, x1, y1);
			}
		}
		else {
			int c = color(random(100,255), random(100,255), random(100,255));
			Block r = new Block(x0,y0,x1,y1, c);
			blocks.add(r);
		}
	}
	
	public class Block {
		public int x0,y0,x1,y1,col;

		/**
		 * @param x0
		 * @param y0
		 * @param x1
		 * @param y1
		 * @param col
		 */
		public Block(int x0, int y0, int x1, int y1, int col) {
			this.x0 = x0;
			this.y0 = y0;
			this.x1 = x1;
			this.y1 = y1;
			this.col = col;
		}
	}
	
	
	public class PointNode {
		public int id;
		public int x;
		public int y;
		/**
		 * @param id
		 * @param x
		 * @param y
		 */
		public PointNode(int id, int x, int y) {
			this.id = id;
			this.x = x;
			this.y = y;
		}		
	}
	
	public class PointNodeXcomparitor implements Comparator<PointNode> {
		@Override
		public int compare(PointNode p0, PointNode p1) {
			long f0 = (p0.x + 10000) * 100000 + p0.y + 10000;
			long f1 = (p1.x + 10000) * 100000 + p1.y + 10000;
			if(f0 == f1)
				return 0;
			else
				return (f0 < f1) ? -1 : 1;
		}	
	}

	public class PointNodeYcomparitor implements Comparator<PointNode> {
		@Override
		public int compare(PointNode p0, PointNode p1) {
			long f0 = (p0.y + 10000) * 100000 + p0.x + 10000;
			long f1 = (p1.y + 10000) * 100000 + p1.x + 10000;
			if(f0 == f1)
				return 0;
			else
				return (f0 < f1) ? -1 : 1;
		}	
	}

}
